/**
 * The contents of this file are subject to the license and copyright detailed in the LICENSE and NOTICE files at the root of the source tree and available online at
 * http://www.askosi.org/license.html
 */
package be.destin.skos.search;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import be.destin.rdf.changes.Status;
import be.destin.skos.core.Concept;
import be.destin.skos.core.ConceptScheme;
import be.destin.skos.core.Count;
import be.destin.skos.core.NoScheme;
import be.destin.skos.core.ReferringApplication;
import be.destin.skos.core.SchemeInterface;
import be.destin.skos.core.SchemeUsage;
import be.destin.skos.core.SkosManager;
import be.destin.skos.html.SkosHtmlManager;
import be.destin.util.Util;

public abstract class IndexationCounter {

	private static final Logger log = Logger.getLogger(IndexationCounter.class);

	String application = null;
	String role = null;
	SkosManager skosManager = null;

	static final public String MultiListDelimiter = "___"; // Prefixes and separates elements in a list of schemes to search

	public abstract Count countUsage (String indexName, String aboutConcept);

	public abstract Locale getSessionLocale (HttpServletRequest request);

	public abstract SchemeInterface getDefaultScheme (String currentIndex);

	public abstract String getSearchTypeScheme();

	public ReferringApplication insureCounts (Concept anEntry) {
		ReferringApplication applicationCounts = anEntry.getApplicationUsage(application);
		if ( applicationCounts == null ) {
			ConceptScheme aScheme = anEntry.getInScheme();
			Collection<SchemeUsage> indexes = aScheme.getUsage().getApplicationUsage(application);
			log.debug("Usage of ("+ application+")"+anEntry.getScheme_About()+"...");
			if (indexes != null && !indexes.isEmpty() ) {
				String magicWord = anEntry.getScheme_About();
				applicationCounts = new ReferringApplication(application);
				int max = 0;
				for (SchemeUsage anIndex : indexes) {
					String indexName = anIndex.getRole();
					Count count = new Count();
					NoScheme anotherScheme = anIndex.getInScheme();
					if ( anotherScheme != null && ! anIndex.getInScheme().equals(aScheme)) {
						String moreMagic = anEntry.getNotation(anotherScheme.getAbout());
						if (moreMagic != null && !moreMagic.isEmpty()) {
							count = countUsage(indexName, moreMagic);
							applicationCounts.addCount(count);
						}
					} else {
						count = countUsage(indexName, magicWord);
						applicationCounts.addCount(count);
					}
					max = ( count.getCount() > max ? count.getCount() : max );
				}
				if (max > 0) { // Default index may be useful too
					Count total = countUsage (null, magicWord);
					if (total != null && total.getCount() > max) { // more total than biggest index!
						Count aCount = applicationCounts.addCount("default",total.getCount(),total.getCategory(),total.isPure());
						CountCacher.store(anEntry,application,aCount);
					}
					log.debug("Usage of ("+ application+")"+anEntry.getScheme_About()+"="+(total==null?"null":total));               
				}
				anEntry.putApplicationUsage(applicationCounts);
			}
		}
		return applicationCounts;
	}

	public void insureCounts (ConceptScheme aScheme) {
		Collection<SchemeUsage> indexes = aScheme.getUsage().getApplicationUsage(application);
		if (indexes != null && !indexes.isEmpty() ) {
			LinkedList<Concept> concepts = aScheme.getConcept(); 
			if(concepts != null && !concepts.isEmpty() ) {
				log.info(aScheme.getAbout()+" : COUNTING ALL USES for "+indexes.size()+" indexes x "+concepts.size()+" concepts.");
				if (Level.DEBUG.equals(log.getEffectiveLevel())) {
					for (SchemeUsage anIndex : indexes) {
						log.debug("index="+anIndex.getRole());
					}
				}
				int maxmax = 0;
				Concept maxmaxEntry = null;
				for (Concept anEntry: concepts) {
					String magicWord = anEntry.getScheme_About();
					ReferringApplication applicationCounts = anEntry.getApplicationUsage(application);
					if (applicationCounts == null) applicationCounts = new ReferringApplication(application); //TODO: pas normal...
					int max = 0;
					for (SchemeUsage anIndex : indexes) {
						String indexName = anIndex.getRole();
						Count count = applicationCounts.getCount(indexName);
						if (count == null) {
							NoScheme anotherScheme = anIndex.getInScheme();
							if ( anotherScheme != null && ! anIndex.getInScheme().equals(aScheme)) {
								String moreMagic = anEntry.getNotation(anotherScheme.getAbout());
								if (moreMagic != null && !moreMagic.isEmpty()) {
									count = countUsage(indexName, moreMagic);
								}
							} else {
								count = countUsage(indexName, magicWord);
							}
						}
						if (count != null) {
							max = ( count.getCount() > max ? count.getCount() : max );
							applicationCounts.addCount(count);
						}
					}
					if (max > 0) { // Default index may be useful too
						if (max > maxmax) {
							maxmax = max;
							maxmaxEntry = anEntry;
						}
						Count total = countUsage (null, magicWord);
						if (total.getCount() > max) { // more total than biggest index!
							Count aCount = applicationCounts.addCount("default",total.getCount(),total.getCategory(),total.isPure());
							CountCacher.store(anEntry,application,aCount);
						}
					}
					anEntry.putApplicationUsage(applicationCounts);
				}
				if (maxmax > 0) {
					log.info ("Max="+maxmaxEntry.getScheme_About()+" x "+maxmax);
				}
				//if (Level.DEBUG.equals(log.getEffectiveLevel())) {
					log.info(aScheme.getAbout()+" : End counting all uses, total="+aScheme.applicationUsageTotal(application));
				//}
			}
		}
	}

	public void insureCounts() { // Counts usage within current application starting from the smallest schemes
		if (skosManager == null) return;
		TreeMap<String,ConceptScheme> littleSchemes = new TreeMap<String,ConceptScheme>();
		for (ConceptScheme aScheme : skosManager.getConceptSchemes()) {
			String sortKey = "000000000"+aScheme.getSize();
			sortKey = sortKey.substring(sortKey.length()-9)+aScheme.getAbout();
			littleSchemes.put(sortKey, aScheme);
		}
		log.debug(littleSchemes);
		for (Entry<String,ConceptScheme> anEntry: littleSchemes.entrySet()) {
			insureCounts (anEntry.getValue());
		}
	}

	public void disCounts(ConceptScheme aScheme) {
		log.debug("Erase counts of application "+application
				+" in scheme "+aScheme.getAbout() );
		// TODO: For Dynamic schemes, DROP the ApplicationUsage cache!
		for (Concept anEntry : aScheme.getConceptInMemory()) {
			ReferringApplication aRefList = anEntry.getApplicationUsage(application);
			if (aRefList != null) {
				aRefList.setCount(null);
			}
		}
	}

	public void disCounts() {
		if (skosManager == null) return;
		for (ConceptScheme aScheme : skosManager.getConceptSchemes()) {
			disCounts (aScheme);
		}
	}

	public void autoComplete (HttpServletRequest req, HttpServletResponse res) throws IOException {

		if (skosManager == null) return;
		SchemeInterface anAuthorityList = null;

		res.setContentType("text/html; charset=UTF-8");
		PrintWriter out = res.getWriter(); 

		// Display the result set as a list
		//      out.println("<HTML><HEAD><TITLE>Phonebook</TITLE></HEAD>");
		//      out.println("<BODY>");
		out.println("<UL>");

		SearchType aSearch = new SearchType();
		aSearch.setAbout(getRole());
		String anAbout = req.getParameter("index");
		if (anAbout != null && !anAbout.isEmpty()) {
			aSearch.setAbout(anAbout);
		}
		int anInputLength = aSearch.getInput();
		if (anInputLength == -1) { // NO list, NO menu
			out.println("</UL>");
			return;
		}
		String listParam = req.getParameter("list");
		if (listParam == null || listParam.isEmpty()) listParam = req.getParameter("scheme");
		if (listParam != null && listParam.length()>0)
			aSearch.setList(listParam);
		String current = req.getParameter("current");
		log.debug("current="+current);
		if (current == null) {
			out.println("</UL>");
			return;
		}
		if (anInputLength == -2) anInputLength = 1; // Input length not initialized: Whatever is received is good to search
		if (current.indexOf('*') >= 0) {
			out.println("</UL>");
			return; // Do not try to autocomplete a keyword containing a Joker
		}
		LabelMatchType matchType = LabelMatchType.STARTS_WITH;
		if (anInputLength == 0) {
			matchType = LabelMatchType.EVERYTHING;
		} else {
			if (current.length() > 0) {
				char c = current.charAt(0);
				if (c == '*') {
					if (current.length() == 1) {
						out.println("</UL>");
						return;
					}
					current = current.substring(1);
					matchType = LabelMatchType.ANYWHERE;
				} else if (c == ' ') {
					if (current.length() == 1) {
						out.println("</UL>");
						return;
					}
					current = current.substring(1);
					matchType = LabelMatchType.INDEPENDANT_PIECES;
				} else if (c == '!') {
					if (current.length() == 1) {
						out.println("</UL>");
						return;
					}
					current = current.substring(1);
					matchType = LabelMatchType.EXACT;
					anInputLength = 0;
				}
			}
		}
		if (current.length() < anInputLength) {
			out.println("</UL>");
			return;
		}

		String compact = req.getParameter("compact");
		log.debug("compact="+compact);
		boolean compactFlag = (compact != null && (compact.equalsIgnoreCase("yes")||compact.equalsIgnoreCase("on")));

		String status = req.getParameter("status");
		log.debug("status="+status);
		Status minStatus = null;
		if (status != null && !status.isEmpty()) {
			try {
				minStatus = Status.valueOf(status);				
			} catch (Exception e){
				// Bad status in parameter...
			}
		}
		if (minStatus == null) minStatus = Status.New_Proposed;

		String aList = aSearch.getList();
		String anIndex = aSearch.getIndex();
		ArrayList<SchemeInterface> listOfAuthorityLists = new ArrayList<SchemeInterface>();
		log.debug("list="+aList+",index="+anIndex);
		if ((aList == null || aList.isEmpty())) {
			log.debug("index="+anIndex);
			if ((anIndex == null || anIndex.isEmpty())) {
				out.println("</UL>");
				return;
			}
			anAuthorityList = this.getDefaultScheme(anIndex);
			if (anAuthorityList != null && anAuthorityList.getClass() != NoScheme.class) {
				listOfAuthorityLists.add(anAuthorityList);
			}
			else log.error("Scheme NOT loaded: " + getApplication() + "/" + anIndex);
		} else {
			String[] schemeCodes = aList.split(MultiListDelimiter+"|[,; ]+");
			for (String oneList : schemeCodes) {
				if (oneList.length() > 0) {
					anAuthorityList = skosManager.openScheme(oneList);
					if (anAuthorityList != null && anAuthorityList.getClass() != NoScheme.class) {
						listOfAuthorityLists.add(anAuthorityList);
					}
					else log.error("Scheme NOT loaded: " + application + "/" + anIndex);
				}
			}
		}

		if (listOfAuthorityLists.isEmpty()) {
			log.error("No available Schemes within: " + application + "/" + aList);
			out.println("</UL>");
			return;
		}

		String mode = req.getParameter("mode");
		log.debug("mode="+mode);
		if ((mode == null || mode.isEmpty())) mode = "search";
		log.debug("AuthorityQueryServlet BEGIN, mode="+mode+",list="+aList+",index="+anIndex+",current="+current);

		String strictlanguage = req.getParameter("strict");
		boolean strictLanguage = false;
		if (strictlanguage != null && !strictlanguage.isEmpty() && strictlanguage.equalsIgnoreCase("on")) strictLanguage = true;
		log.debug("mode="+mode);
		String userLanguage = this.getSessionLocale(req).getLanguage();

		int i=0;
		SkosHtmlManager skosHtmlManager = new SkosHtmlManager(req,userLanguage,skosManager,getApplication());
		for (SchemeInterface theAuthorityList : listOfAuthorityLists) {
			log.debug("Scheme "+theAuthorityList.getAbout()+"="+theAuthorityList.getClass().getName());
			Collection<SearchResult> tri = theAuthorityList.labelMatch(current,userLanguage, strictLanguage, matchType);
			log.debug(tri.size()+" concepts: "+tri);
			if (tri.isEmpty()) continue;
			String icon = skosHtmlManager.toHtmlImage(theAuthorityList.getIconAdapted(skosHtmlManager),null, false);

			int maxNoShow = 1;
			if ("edit".equals(mode)) maxNoShow = 0;
			for (SearchResult oneResult: tri) {
					Concept entry = skosManager.getScheme_Concept(oneResult.getAbout());
					if (entry == null) continue;
					Status entryStatus = entry.getCurrentStatus();
					if (entryStatus == null) entryStatus = Status.Published;
					else if (entryStatus == Status.Not_Approved) continue;
					if (entryStatus.ordinal() < minStatus.ordinal()) continue;
					long count = 0;
					ReferringApplication refCount = insureCounts(entry);
					if (anIndex == null || "".equals(anIndex)) {
						if (refCount != null) count = refCount.getTotal();
					} else {
						if (refCount != null) {
							count = refCount.getCountCount(anIndex);
						}
						//count = DSpaceSkosManager.countUsage(context, index, toStore);
					}
					if (count >= maxNoShow) {
						String toShow = Util.escapeHTMLEntities(oneResult.toFullOption() + (count == maxNoShow? "" : (" ("+count+")")));
						out.println("<LI id=\"" + (compactFlag ? entry.getAbout() : oneResult.getAbout()) + "\">"
								+ entryStatus.toHtml(skosHtmlManager)
								+ icon
								+ toShow + "</LI>" );  
						i++;
					}
			} 
		}
		out.println("</UL>");
		//log.debug(i+" concepts for "+current+", last="+toShow+",stored as:"+toStore);
		//      out.println("</BODY></HTML>");
	}

	public String selectOptions (String anIndexCode, String aSchemeAbout, String currentConceptAbout, String userLanguage, boolean strictLanguage, boolean unusedIncluded) {

		if (skosManager == null) return "";
		SchemeInterface anAuthorityList = null;

		StringBuffer out = new StringBuffer();
		SearchType aSearch = new SearchType();
		aSearch.setAbout(anIndexCode);
		if (aSchemeAbout != null && aSchemeAbout.length()>0)
			aSearch.setList(aSchemeAbout);
		LabelMatchType matchType = LabelMatchType.STARTS_WITH;
		if (currentConceptAbout.length() == 0) {
			matchType = LabelMatchType.EVERYTHING;
		} else {
			char c = currentConceptAbout.charAt(0);
			if (c == '*') {
				if (currentConceptAbout.length() == 1) {
					matchType = LabelMatchType.EVERYTHING;
				} else {
					currentConceptAbout = currentConceptAbout.substring(1);
					matchType = LabelMatchType.ANYWHERE;
				}
			} else if (c == ' ') {
				if (currentConceptAbout.length() == 1) {
					return "";
				}
				currentConceptAbout = currentConceptAbout.substring(1);
				matchType = LabelMatchType.INDEPENDANT_PIECES;
			} else if (c == '!') {
				if (currentConceptAbout.length() == 1) {
					return "";
				}
				currentConceptAbout = currentConceptAbout.substring(1);
				matchType = LabelMatchType.EXACT;
			}
		}

		String aList = aSearch.getList();
		String anIndex = aSearch.getIndex();
		ArrayList<SchemeInterface> listOfAuthorityLists = new ArrayList<SchemeInterface>();
		log.debug("list="+aList+",index="+anIndex);
		if ((aList == null || aList.isEmpty())) {
			log.debug("index="+anIndex);
			if ((anIndex == null || anIndex.isEmpty())) {
				return "";
			}
			anAuthorityList = this.getDefaultScheme(anIndex);
			if (anAuthorityList != null && anAuthorityList.getClass() != NoScheme.class) {
				listOfAuthorityLists.add(anAuthorityList);
			}
			else log.error("Scheme NOT loaded: " + getApplication() + "/" + anIndex);
		} else {
			String[] schemeCodes = aList.split(MultiListDelimiter+"|[,; ]+");
			for (String oneList : schemeCodes) {
				if (oneList.length() > 0) {
					anAuthorityList = skosManager.openScheme(oneList);
					if (anAuthorityList != null && anAuthorityList.getClass() != NoScheme.class) {
						listOfAuthorityLists.add(anAuthorityList);
					}
					else log.error("Scheme NOT loaded: " + application + "/" + anIndex);
				}
			}
		}

		if (listOfAuthorityLists.isEmpty()) {
			log.error("No available Schemes within: " + application + "/" + aList);
			return "";
		}

		int i=0;
		for (SchemeInterface theAuthorityList : listOfAuthorityLists) {
			log.debug("Scheme "+theAuthorityList.getAbout()+"="+theAuthorityList.getClass().getName());
			Collection<SearchResult> tri = theAuthorityList.labelMatch(currentConceptAbout,userLanguage, strictLanguage, matchType);
			log.debug(tri.size()+" concepts: "+tri);
			if (tri.isEmpty()) continue;

			for (SearchResult oneResult: tri) {
					Concept entry = skosManager.getScheme_Concept(oneResult.getAbout());
					if (entry != null) {
						long count = 0;
						ReferringApplication refCount = insureCounts(entry);
						if (anIndex == null || "".equals(anIndex)) {
							if (refCount != null) count = refCount.getTotal();
						} else {
							if (refCount != null) {
								count = refCount.getCountCount(anIndex);
							}
							//count = DSpaceSkosManager.countUsage(context, index, toStore);
						}
						if (unusedIncluded || count > 0) {
							String toShow = Util.escapeHTMLEntities(oneResult.toOption() + (count <= 1? "" : (" ("+count+")")));
							out.append("<OPTION value=\"" + oneResult.getAbout() + "\">"
									+ toShow + "</OPTION>" );  
							i++;
						}
					}
			} 
		}
		//log.debug(i+" concepts for "+currentConceptAbout+", last="+toShow+",stored as:"+toStore);
		return out.toString();
	}

/**
	 * @return the application
	 */
	public String getApplication() {
		return application;
	}

	/**
	 * @param application the application to set
	 */
	public void setApplication(String application) {
		this.application = application;
	}

	/**
	 * @return the role
	 */
	public String getRole() {
		return role;
	}

	/**
	 * @param role the role to set
	 */
	public void setRole(String role) {
		this.role = role;
	}

	public void setSkosManager(SkosManager skosManager) {
		this.skosManager = skosManager;
	}

	public LinkedList<SearchType> getAvailableSearches (HttpServletRequest request) {
		if (skosManager == null) return null;
		LinkedList<SearchType> result = new LinkedList<SearchType>();
		SchemeInterface aScheme = skosManager.openScheme(this.getSearchTypeScheme());
		String userLanguage = this.getSessionLocale(request).getLanguage();
		SkosHtmlManager skosHtmlManager = new SkosHtmlManager(request,userLanguage,skosManager,application);
		if (aScheme == null) return result;
		if (aScheme.getClass() == NoScheme.class) return result;
		Collection<Concept> concepts = aScheme.getAllConcepts();
		for (Concept aConcept : concepts) {
			SearchType aSearch = new SearchType();
			aSearch.setLabel(aConcept.getPrefLabel(userLanguage, false));
			aSearch.setAbout(aConcept.getAbout());
			aSearch.setIcon(aConcept.getIconAdapted(skosHtmlManager));
			result.add(aSearch);
		}
		return result;
	}
}
